package com.tn.rtdata.performance.ui;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

import com.tn.rtdata.DataEvent;
import com.tn.rtdata.DataListener;
import com.tn.rtdata.performance.Client;
import com.tn.rtdata.performance.ClientDef;

/**
 * The main frame of the RT_Data Performance application.
 */
public class MainFrame extends JFrame
{
  private static final Dimension DEFAULT_SIZE = new Dimension(800, 600);
  private static final MessageFormat FORMAT_CLIENT_COUNT = new MessageFormat("{0} clients");
  private static final String TITLE = "RT-Data Performance";

  /**
   * Creates a new <code>MainFrame</code>.
   */
  public MainFrame(Collection<ClientDef> clientDefs)
  {
    setTitle(TITLE);
    setSize(DEFAULT_SIZE);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    createControls(clientDefs);
    setVisible(true);
  }

  /**
   * Creates the UI controls.
   */
  private void createControls(Collection<ClientDef> clientDefs)
  {
    JLabel clientCountLabel = new JLabel(FORMAT_CLIENT_COUNT.format(new Object[]{0}));

    ClientTableModel clientTableModel = new ClientTableModel(clientCountLabel);
    addWindowListener(new ClientStoppingWindowListener(clientTableModel.clients));

    TableColumn typeTableColumn = new TableColumn(0);
    typeTableColumn.setHeaderValue("Client Type");

    TableColumn fillTableColumn = new TableColumn(1);
    fillTableColumn.setHeaderValue("Fill");

    TableColumn sessionIdTableColumn = new TableColumn(2);
    sessionIdTableColumn.setHeaderValue("Session ID");

    TableColumn updatesColumn = new TableColumn(3);
    updatesColumn.setHeaderValue("Updates");

    TableColumn createdColumn = new TableColumn(4);
    createdColumn.setHeaderValue("Created");

    TableColumnModel clientTableColumnModel = new DefaultTableColumnModel();
    clientTableColumnModel.addColumn(typeTableColumn);
    clientTableColumnModel.addColumn(fillTableColumn);
    clientTableColumnModel.addColumn(sessionIdTableColumn);
    clientTableColumnModel.addColumn(updatesColumn);
    clientTableColumnModel.addColumn(createdColumn);

    JTable clientTable = new JTable(clientTableModel, clientTableColumnModel);

    JComboBox clientTypesComboBox = new JComboBox(clientDefs.toArray());
    clientTypesComboBox.setRenderer(new ClientDefListCellRenderer());

    JTextField clientCountTextField = new JFormattedTextField(NumberFormat.getInstance());
    clientCountTextField.setMinimumSize(new Dimension(40, 20));
    clientCountTextField.setPreferredSize(new Dimension(40, 20));
    clientCountTextField.setMaximumSize(new Dimension(40, 20));
    clientCountTextField.setText("1");

    JButton addButton = new JButton("Add");
    addButton.addActionListener(new AddClientActionListener(clientTypesComboBox, clientCountTextField, clientTableModel));

    JButton removeButton = new JButton("Remove");
    removeButton.addActionListener(new RemoveClientActionListener(clientTable));

    JPanel clientButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
    clientButtonPanel.add(clientTypesComboBox);
    clientButtonPanel.add(new JLabel("x"));
    clientButtonPanel.add(clientCountTextField);
    clientButtonPanel.add(addButton);
    clientButtonPanel.add(removeButton);

    JPanel clientPanel = new JPanel(new BorderLayout());
    clientPanel.add(new JScrollPane(clientTable), BorderLayout.CENTER);
    clientPanel.add(clientButtonPanel, BorderLayout.SOUTH);

    JPanel statusPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
    statusPanel.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.GRAY));
    statusPanel.add(clientCountLabel);

    getContentPane().setLayout(new BorderLayout());
    getContentPane().add(clientPanel, BorderLayout.CENTER);
    getContentPane().add(statusPanel, BorderLayout.SOUTH);
  }

  /**
   * An implementation of <code>TableModel</code> that manages <code>Client</code>s.
   */
  private class ClientTableModel extends AbstractTableModel
  {
    private static final int COLUMN_TYPE = 0;
    private static final int COLUMN_FILL = 1;
    private static final int COLUMN_SESSION_ID = 2;
    private static final int COLUMN_UPDATES = 3;
    private static final int COLUMN_CREATED = 4;
    private static final int COLUMN_COUNT = 5;

    private JLabel clientCountLabel;
    private List<Client> clients;

    /**
     * Creates a new <code>ClientTableModel</code>.
     */
    public ClientTableModel(JLabel clientCountLabel)
    {
      this.clientCountLabel = clientCountLabel;
      this.clients = new ArrayList<Client>();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getRowCount()
    {
      return clients.size();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getColumnCount()
    {
      return COLUMN_COUNT;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object getValueAt(int rowIndex, int columnIndex)
    {
      switch(columnIndex)
      {
        case COLUMN_TYPE:
          return clients.get(rowIndex).getDisplayName();

        case COLUMN_FILL:
          return clients.get(rowIndex).getFillName();

        case COLUMN_SESSION_ID:
          return clients.get(rowIndex).getSessionId();

        case COLUMN_UPDATES:
          return clients.get(rowIndex).getUpdateCount();

        case COLUMN_CREATED:
          return clients.get(rowIndex).getCreated();

        default:
          return null;
      }
    }

    /**
     * Adds the <code>clients</code>.
     */
    private void addClient(Client client)
    {
      clients.add(client);
      fireTableDataChanged();
      clientCountLabel.setText(FORMAT_CLIENT_COUNT.format(new Object[]{clients.size()}));
    }

    /**
     * Adds the <code>clients</code>.
     */
    private void removeAllClients(List<Client> clients)
    {
      this.clients.removeAll(clients);
      fireTableDataChanged();
      clientCountLabel.setText(FORMAT_CLIENT_COUNT.format(new Object[]{this.clients.size()}));
    }
  }

  /**
   * An implementation of <code>ListCellRenderer</code> that uses the <i>type</i> of a <code>ClientDef</code> as the text.
   */
  private class ClientDefListCellRenderer extends DefaultListCellRenderer
  {
    @Override
    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus)
    {
      JLabel label = (JLabel)super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
      label.setText(((ClientDef)value).getDisplayName());

      return label;
    }
  }

  /**
   * An implementation of <code>ActionListener</code> that creates a new <code>Client</code> based on the selected <code>ClientDef</code>.
   */
  private class AddClientActionListener implements ActionListener
  {
    private JTextField clientCountTextField;
    private ClientTableModel clientTableModel;
    private JComboBox clientTypesComboBox;

    /**
     * Creates a new <code>AddClientActionListener</code>.
     */
    public AddClientActionListener(JComboBox clientTypesComboBox, JTextField clientCountTextField, ClientTableModel clientTableModel)
    {
      this.clientTypesComboBox = clientTypesComboBox;
      this.clientCountTextField = clientCountTextField;
      this.clientTableModel = clientTableModel;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void actionPerformed(ActionEvent actionEvent)
    {
      int clientCount = Integer.parseInt(clientCountTextField.getText());

      while (clientCount > 0)
      {
        Client client = new Client((ClientDef)clientTypesComboBox.getSelectedItem());

        clientTableModel.addClient(client);
        client.addDataListener(
          new DataListener<Client>()
          {
            @Override
            public void dataEvent(DataEvent dataEvent)
            {
              //noinspection SuspiciousMethodCalls
              clientTableModel.fireTableCellUpdated(clientTableModel.clients.indexOf(dataEvent.getNewValue()), ClientTableModel.COLUMN_UPDATES);
            }
          }
        );

        clientCount--;
      }
    }
  }

  /**
   * An implementation of <code>ActionListener</code> that removes the selected clients.
   */
  private class RemoveClientActionListener implements ActionListener
  {
    private JTable clientTable;

    /**
     * Creates a new <code>RemoveClientActionListener</code>.
     */
    public RemoveClientActionListener(JTable clientTable)
    {
      this.clientTable = clientTable;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void actionPerformed(ActionEvent actionEvent)
    {
      ClientTableModel clientTableModel = (ClientTableModel)clientTable.getModel();
      List<Client> clients = new ArrayList<Client>();

      for (int selectedRow : clientTable.getSelectedRows())
      {
        Client client = clientTableModel.clients.get(selectedRow);
        client.stop();
        clients.add(client);
      }

      clientTableModel.removeAllClients(clients);
    }
  }

  /**
   * An implementation of <code>WindowListener</code> that stops any running clients.
   */
  private class ClientStoppingWindowListener extends WindowAdapter
  {
    private List<Client> clients;

    /**
     * Creates a new <code>
     */
    public ClientStoppingWindowListener(List<Client> clients)
    {
      this.clients = clients;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void windowClosing(WindowEvent windowEvent)
    {
      for (Client client : clients)
      {
        client.stop();
      }
    }
  }
}
